package com.autonavi.yunda.yunji.common.utils.http;

import com.alibaba.security.SecurityUtil;
import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.common.utils.http.ssl.SSLSocketClient;
import com.autonavi.yunda.yunji.common.utils.http.vo.HttpResponse;
import com.autonavi.yunda.yunji.core.engine.handler.vo.HttpOptionVO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.Buffer;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.HttpMethod;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;


/**
 * @author cizhong.wcs
 * @date 2022/1/25 10:57 上午
 */
@Slf4j
public class HttpClient {
    private final OkHttpClient client;

    private static final int DEFAULT_TIMEOUT = 30 * 1000;

    public HttpClient(OkHttpClient client) {
        this.client = client;
    }

    public HttpClient() {
        OkHttpClient.Builder builder = basicBuilder();
        this.client = builder.build();
    }

    /**
     * @param maxIdleConnections 最大空闲连接数
     * @param maxRequests        最大请求数
     * @param maxRequestsPerHost 同一目标主机的最大请求数
     */
    public HttpClient(int maxIdleConnections, int maxRequests, int maxRequestsPerHost) {
        OkHttpClient.Builder builder = basicBuilder();
        // 配置连接池大小
        ConnectionPool connectionPool = new ConnectionPool(maxIdleConnections, 5, TimeUnit.MINUTES);
        // 配置线程池大小
        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(maxRequests);
        dispatcher.setMaxRequestsPerHost(maxRequestsPerHost);
        this.client = builder
                .connectionPool(connectionPool)
                .dispatcher(dispatcher)
                .build();

    }

    /*
     * This returns a client that shares the same connection pool, dispatcher, and configuration with the original client.
     */
    public HttpClient newClient(HttpOptionVO httpOptionVO) {
        Integer timeout = httpOptionVO.timeout;
        Integer retryCount = httpOptionVO.retryTimes;
        Boolean ignoreSSL = httpOptionVO.ignoreSSL;
        timeout = timeout == null ? DEFAULT_TIMEOUT : timeout;
        OkHttpClient.Builder builder = client.newBuilder()
                .followRedirects(false)
                .connectTimeout(timeout, TimeUnit.MILLISECONDS)
                .writeTimeout(timeout, TimeUnit.MILLISECONDS)
                .readTimeout(timeout, TimeUnit.MILLISECONDS);
//        if (retryCount != null && retryCount > 0) {
//            builder.addInterceptor(new OkHttpRetryInterceptor(retryCount));
//        }
        if (Boolean.TRUE.equals(httpOptionVO.useHttp_1)) {
            builder.protocols(List.of(Protocol.HTTP_1_1));
        }
        if (Boolean.TRUE.equals(ignoreSSL)) {
            builder.sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager());
            builder.hostnameVerifier(SSLSocketClient.getHostnameVerifier());
        }
        return new HttpClient(builder.build());
    }


    private OkHttpClient.Builder basicBuilder() {
        return new OkHttpClient().newBuilder()
                .followRedirects(false)
                .connectTimeout(HttpClient.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .writeTimeout(HttpClient.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .readTimeout(HttpClient.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
    }

    public HttpResponse post(String url, String body, Map<String, String> headers, String contentType) {
        return postOrPut(url, body, headers, contentType, POST);
    }

    public HttpResponse put(String url, String body, Map<String, String> headers, String contentType) {
        return postOrPut(url, body, headers, contentType, PUT);
    }

    private HttpResponse postOrPut(String url, String body, Map<String, String> headers, String contentType, HttpMethod method) {
        log.debug("post  url = {},body = {},headers = {},contentType = {}", url, body, JsonUtils.toString(headers), contentType);
        contentType = Strings.isNotBlank(contentType) ? contentType : "application/json";
        Request.Builder builder = new Request.Builder();
        builder.url(url);
        Optional.ofNullable(headers).ifPresent(h ->
                h.entrySet().stream().filter(e -> e.getValue() != null).forEach(e -> builder.addHeader(e.getKey(), e.getValue()))
        );
        MediaType mediaType = MediaType.parse(contentType);
        RequestBody requestBody = RequestBody.create(body == null ? "".getBytes() : body.getBytes(StandardCharsets.UTF_8), mediaType);
        if (POST.equals(method)) {
            builder.post(requestBody);
        } else {
            builder.put(requestBody);
        }
        Request request = builder.build();
        return doExecute(request);
    }

    public HttpResponse get(String url, Map<String, String> headers) {
        Request.Builder builder = new Request.Builder();
        builder.url(url);
        log.info("get complete url = {}", url);
        Optional.ofNullable(headers).ifPresent(h -> h.forEach(builder::addHeader));
        Request request = builder.build();
        return doExecute(request);
    }

    public HttpResponse doExecute(Request request) {
        boolean checkResult = SecurityUtil.checkSSRFWithoutConnection(request.url().url().toString(), true);
        if (!checkResult) {
            log.warn("unsafe url.. checkSSRF error ,url = {}", request.url().url());
        }
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new AmapException(AmapExceptionCode.COMMON_HTTP_ERROR, "http send failed .. response code is :" + response.code() + " response message is :" + response.message());
            }
            return new HttpResponse(HttpUtil.getHeaders(response), Objects.requireNonNull(response.body()).string());
        } catch (IOException e) {
            throw new AmapException(AmapExceptionCode.COMMON_HTTP_ERROR, "http send error message = " + e.getMessage()
                    + " ,request url = " + request.url()
                    + " ,request body = " + getRequestBodyContent(request), e);
        }
    }

    private String getRequestBodyContent(Request request) {
        if (request == null || request.body() == null) {
            return "";
        }
        final Buffer buffer = new Buffer();
        try {
            Objects.requireNonNull(request.body()).writeTo(buffer);
        } catch (Exception e) {
            log.error("getRequestBodyContent error", e);
            return "";
        }
        return buffer.readUtf8();
    }
}
